/*
 * @Author: your name
 * @Date: 2021-10-12 14:10:29
 * @LastEditTime: 2021-10-12 15:01:23
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /Desktop/model.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>

#define MAX_LINE 80 //The maximu length command

#define normal			0   
#define out_redirect	1   
#define in_redirect		2   
#define have_pipe		3  

char command[MAX_LINE]; // the command that was entered

// redirect variable
char *fileName;
// if input >
int redirect_out = 0;
// if input <
int redirect_in = 0;
int in_index = 0;

// pipe variable
char *pipe_args[MAX_LINE/2 + 1];
int pipe_num = 0;
int cmd_in_pipe = 0;


void print_prompt()
{
	printf("$");
}

int get_input_wait(char command[], char *args[])
{
	/* read a command from the keyboard */
	read(0, command, MAX_LINE);

	/* parse the command */
	int cmd_index = 1;
	args[0] = command; // store the first word's pointer
	for (int i = 0; i < MAX_LINE; i++) {
		// end of word
		if (command[i] == ' ') {
			command[i] = '\0';
			args[cmd_index] = &command[i+1];
			cmd_index++;
		}
		// the last word
		else if (command[i] == '\n') {
			command[i] = '\0';
			args[cmd_index] = NULL;
			break;
		}
	}

	/* Check &, whether need to go background */
	int need_wait = 1;
	if (!strcmp(args[cmd_index-1], "&")) {
		need_wait = 0;
		args[cmd_index-1] = NULL; // Remove the '&'
	}
	return need_wait;

}

int if_quit(char *args[], int should_run) {
	/* Judge Quit */
	if (!strcmp(args[0], "quit")) {
		printf("Programme quit\n");
		should_run = 0;
		return 1;
	}
}


void praste(char *arr1[], char *arr2[])
{
	int i = 0;
	while (arr2[i] != NULL) {
		char *now = arr2[i];
		char *last = (char*)malloc(sizeof(char) * MAX_LINE);
		for (int j = 0; j < MAX_LINE; j++) {
			last[j] = *now;
			now++;
		}
		arr1[i] = last;
		i++;
	}
	arr1[i] = NULL;
}


void showArgs(char *args[]) 
{
	int i = 0;
	while (args[i] != NULL) 
	{
		printf("%s", args[i]);
		i++;
	}
	printf("\n");
}


void judge_redirect(char *args[])
{
	
	while (args[in_index] != NULL) {
		// judge in or out
		if (!strcmp(args[in_index], ">")) {
			redirect_out = 1;
			break;
		} else if (!strcmp(args[in_index], "<")) {
			redirect_in = 1;
			break;
		}
		in_index++;
	}

	
	if (redirect_in | redirect_out) {
		fileName = args[in_index+1];
		// let & to NULL
		args[in_index] = NULL;
	}
}

void action_redirect()
{   
    int file_direction;
    int fid;
    if (redirect_out) {
        // Open file - store output
        if ((fid = open(fileName, O_CREAT|O_WRONLY, 0666)) == -1) {
            printf("open error\n");
            exit(-1);
        }
        file_direction = dup2(fid, STDOUT_FILENO); // output will go to file
        if (file_direction == -1) { 
			// catch error
            printf("dup2 error\n");
            exit(-1);
        }
    } else if (redirect_in) {
        if ((fid = open(fileName, O_RDONLY)) == -1) {
            printf("open error\n");
            exit(-1);
        }
		// input the file name and content
        file_direction = dup2(fid, STDIN_FILENO);
        if (file_direction == -1) { 
			// catch error
            printf("dup2 error\n");
            exit(-1);
        }
    }
}

// if has pipe or not
int use_pipe(char *args[], char *pipe_args[], int pipe_num, int cmd_in_pipe) {
	pipe_num = 0;
    while (args[pipe_num] != NULL) {
        if (!strcmp(args[pipe_num], "|")) {
            cmd_in_pipe = 1;
            break;
        }
        pipe_num++;
    }
    // Have the pip in the command
    if (cmd_in_pipe) {
        int args1_index = pipe_num + 1;
        int args2_index = 0;
        while (args[args1_index] != NULL) {
            pipe_args[args2_index++] = args[args1_index++];
        }
        args[pipe_num] = NULL;
		// pipe_args stop
        pipe_args[args2_index] = NULL;
    }
    return cmd_in_pipe;
}




int main(void) {
	// command line of 80 has max of 40 arguments
	char *args[MAX_LINE/2 + 1];
	args[0] = NULL;
	
	// history
	char *history_args[MAX_LINE/2 + 1];
	history_args[0] = NULL;
	

	// flag to determine when to exit program
	int should_run = 1;
	
	while(should_run) {
		print_prompt();
		fflush(stdout);
		int cmd_index = 1;
		int need_wait;
		// read a command from the keyboard & parse the command & check wait
		need_wait = get_input_wait(command, args);
		
		// Judge Quit
		int ifQuit;
		ifQuit = if_quit(args, should_run);
		if (ifQuit == 1)
		{
			/* code */
			break;
		}
		
		// Do history
		if (!strcmp(args[0], "!!")) {
			// if do not have command
			if (history_args[0] == NULL) {
			    printf("No commands in history buffer\n");
			    continue;
			} else { // if input commands
			    // load in history
			    praste(args, history_args);
			    // output the cmd
			    printf("History Commands: ");
			    showArgs(args);
			}
		 } else {
			praste(history_args, args); // Store the history
		 }
		
		
		// judge redirect
		judge_redirect(args);


		// if has pipe or not
		cmd_in_pipe = use_pipe(args, pipe_args, pipe_num, cmd_in_pipe);



		/*
		 *After reading user input, the steps are:
		 *(1) fork a child process using fork()
		 *(2) the child process will invoke execvp()
		 *(3) parent will invoke wait() unless command included &
		 */

		// Create child process
        	pid_t pid; // child process id
        	int status; // child process statues
        	pid = fork(); // create child process now
        	// If something error, child process can not be create
        	if (pid < 0) {
            	printf("Parent: fork failed\n");
            	return 1; // This "return" is for Parent
        	}
        	else if(pid == 0) { 
			// printf("Child: now running redirection.");
            
			// printf("Child: now running the program, doing exec\n");
            
			// action redirection
			action_redirect();

			// has pipe
			if (cmd_in_pipe) {
				/* Create a child-child process do the args first
				then child process do the args2 */
				int file_2;
				int cc_process_id;
				int cc_process_state;
				int tid;
				// child fork a new child
				cc_process_id = fork();
				if (cc_process_id < 0) {
					printf("Child's child: Fork Failed\n");
					return 1;
				}
				// Here is Child-Child
				else if (cc_process_id == 0) {
					if ((tid = open("/tmp/1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666)) == -1) {
						printf("Open Error\n");
						exit(-1);
					}
					// temp to store command
					file_2 = dup2(tid, STDOUT_FILENO);
					execvp(args[0],args);
					exit(0);
				}
				// Here is the previous child (fork caller)
				else {
					waitpid(cc_process_id, &cc_process_state, 0);
					close(file_2);
					if ((file_2 = open("/tmp/1.txt", O_RDONLY)) == -1) {
						printf("open error\n");
						exit(-1);
					}
					// input into temp file
					dup2(file_2, STDIN_FILENO);
					execvp(pipe_args[0], pipe_args); // execute the second one
					if (remove("/tmp/1.txt") < 0) {
						printf("remove error\n");
						exit(-1);
					}
					exit(0);
				}
			}
			// if no pipe
			else {
				 // execute command
				execvp(args[0], args);
				printf("Child: exec failed, die\n");
				return 1; // This "return" is for Child
			}


			execvp(args[0], args);
			
			// if exec succeeded, the new child now starts executing the code of 
			// the main function of the xcalc program
			// The new child normally never executes the code below , unless exec failed.
			return 1;  // child process dies after failed exec.
		} else {
			if (need_wait) {
				wait(&status); // Waiting child process finished
			}
		}
	}
	return 0;
}
